// Copyright (C) 2010  Joan Puigcerver Perez, <joapuipe@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <TCPClientSession.hpp>

#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>

#include <SAXRequestsHandler.hpp>

const size_t TCPClientSession::buffersize = 10240; /* 10KBytes */

TCPClientSession::TCPClientSession(const Database * db, ost::TCPSocket &server,
				   timeout_t timeout, std::ostream& logf, 
				   ost::Mutex& logm) 
  : ost::TCPSession(server), database(db), 
    client_ip(getIPV4Peer().getHostname()), logfile(logf), log_mutex(logm)
{
  setTimeout(timeout);
  request = new char [ buffersize ];
  memset(request, 0x00, buffersize);
}

TCPClientSession::~TCPClientSession()
{
  delete [] request;
}

void TCPClientSession::run(void)
{
  int err = 0;

  build_header_response();

  err = get_request();

  if ( !err ) 
    err = process_request();
  
  build_end_response();
  err = send_response();
}

void TCPClientSession::final(void)
{
  print_log_line( client_ip + ": Clossing connection", logfile, log_mutex );
  disconnect();
}

int TCPClientSession::get_request(void)
{
  char buffer[50] = { '\0' };
  
  req_size = 0;
  tcp()->clear();

  print_log_line( client_ip + ": Waiting for request...", logfile, log_mutex );

  // Read request size
  tcp()->getline(buffer, 50);
  sscanf(buffer, "%u", &req_size);
  if ( req_size == 0 || req_size > buffersize ) {
    build_error_response(101, "Ivalid request size.");
    return 1;
  }

  // Read request
  size_t received=0;
  while( received < req_size && !tcp()->eof() ) {
    tcp()->read(request+received, req_size-received);
    received += (size_t)tcp()->gcount();
  }
  
  if ( received != req_size ) {
    build_error_response(102, "I received less bytes than you sent.");
    return 2;
  }

  print_log_request(request);
   
  return 0;
}

int TCPClientSession::send_response(void)
{
  const std::string response_str = response.str();

  print_log_line( client_ip + ": Sending response...", logfile, log_mutex );

  print_log_response(response_str);

  tcp()->clear();
  (*tcp()) << response_str.length() << std::endl;
  tcp()->write(response_str.c_str(), response_str.length());

  return (tcp()->fail() ? 1 : 0);
}


int TCPClientSession::process_request(void)
{  
  print_log_line( client_ip + ": Parsing request...", logfile, log_mutex );

  try {
    XMLPlatformUtils::Initialize();
  } catch ( ... ) {
    build_error_response(201, "XML Internal Error. Try again in few seconds.\
 If the problem persists report it to the sysadmin.");
    return 1;
  }
  

  // SAX2 Parser
  SAX2XMLReader* parser = XMLReaderFactory::createXMLReader(); 
  parser->setFeature(XMLUni::fgSAX2CoreNameSpaces, true);
  parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
  parser->setFeature(XMLUni::fgXercesSchemaFullChecking, true);
  XMLCh * schema = XMLString::transcode("http://www.jpuigcerver.net/allen/xml/request.xsd xml/request.xsd");
  parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation, schema);

  
  // SAX2 Handler
  SAXRequestsHandler * handler = new SAXRequestsHandler;
  parser->setContentHandler(handler);
  parser->setErrorHandler(handler);


  // Input from memory
  MemBufInputSource * memBufIS = new MemBufInputSource((XMLByte*)request, 
				     (XMLSize_t)strlen(request), 
				     client_ip.c_str(), 
				     false);
  
  int error = 0;
  try {
    parser->parse(*memBufIS);
    
    print_log_line( client_ip + ": Obtaining response...", logfile, log_mutex );

    // Filter to be used in the query.
    Filter filter = Filter(handler->getMinYearFilter(), 
			   handler->getMaxYearFilter(), 
			   handler->getSuspendedFilter(), 
			   handler->getMediasFilter(), 
			   handler->getGenresFilter(), 
			   handler->getWorksFilter()
			   );
    
    switch ( handler->getRequestType() ) {
    case SAXRequestsHandler::R_DISTANCE: // Distance between two persons
      build_distance_response(handler->getSource(), 
			      handler->getTarget(), filter);
      break;
    case SAXRequestsHandler::R_DISTANCEN: // Persons at a N distance from a given one.
      build_distanceN_response(handler->getSource(), 
			       handler->getHops(), filter);
      break;
    case SAXRequestsHandler::R_CENTER: // Average number of hops for a given person.
      build_center_response(handler->getSource(), filter);
      break;
    }    
  }  
  catch ( const SAXParseException& except ) {
    char * msg = XMLString::transcode(except.getMessage());
    build_error_response(202, ("XML Parsing Error." + std::string(msg)));
    XMLString::release(&msg);
    error = 2;
  }
  catch ( ... ) {
    build_error_response(203, "XML Parsing Error.\
 Unknown error: there is something weird in your XML request.");
    error = 3;
  }

  XMLString::release(&schema);  
  delete memBufIS;
  delete handler;
  delete parser;
  
  XMLPlatformUtils::Terminate();
  
  return error;
}

void TCPClientSession::build_header_response(void)
{
  response << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
	   << "<response xmlns=\"http://www.jpuigcerver.net/xml/response.xsd\" version=\"0.1\">";
}

void TCPClientSession::build_end_response(void)
{
  response << "</response>";
}

void TCPClientSession::build_error_response(const unsigned int code, const std::string& message)
{
  response << "<error code=\"" << code << "\">"
	   << "<message>" << message << "</message>"
	   << "</error>";
}

void TCPClientSession::build_explain_response(const std::string& name, const std::pair< bool, std::list<const Person *> >& lst)
{
  if ( !lst.first || lst.second.size() != 1) {
    response << "<person name=\"" << xml_string_escape(name) << "\" found=\"" << (lst.first ? "true" : "false") << "\">";
    for(std::list<const Person*>::const_iterator it = lst.second.begin(); it != lst.second.end(); ++it)
      response << "<name>" << xml_string_escape( (*it)->name() ) << "</name>";
    response << "</person>";
  }
}

void TCPClientSession::build_distance_response(const std::string& source, const std::string& target, const Filter& filter)
{
  // Look for first person
  std::pair< bool, std::list<const Person *> > src_lst = database->search_person(source);  
  // Look for second person
  std::pair< bool, std::list<const Person *> > tgt_lst = database->search_person(target);  
  
  // If some of previous persons are not found or their names match more than once, then build an explanation response.
  if ( !src_lst.first || src_lst.second.size() != 1 || !tgt_lst.first || tgt_lst.second.size() != 1 ) {
    response << "<explain>";
    build_explain_response(source, src_lst);
    build_explain_response(target, tgt_lst);
    response << "</explain>";
  } else {
    // Obtain the distance between both persons.
    Database::DistanceResult result = database->distance( src_lst.second.front(), tgt_lst.second.front(), filter );
    
    // Response message...
    response << "<distance source=\"" << xml_string_escape(source)
    	     << "\" target=\"" << xml_string_escape(target)
    	     << "\" hops=\"" << result.first << "\">";
    
    // Iterates through the links to print the path.
    for(std::list< std::pair <const Link*,const Link*> >::const_iterator it = result.second.begin(); 
	it != result.second.end(); ++it) {
      response << "<link movie=\"" << xml_string_escape(it->first->movie()->title()) << "\">" 
    	       << "<source work=\"" << it->first->work() << "\">" 
    	       << xml_string_escape(it->first->person()->name()) << "</source>"
    	       << "<target work=\"" << it->second->work() << "\">"
    	       << xml_string_escape(it->second->person()->name()) << "</target></link>";      
    }
    response << "</distance>";
  }
}

void TCPClientSession:: build_distanceN_response(const std::string& source, const uint32_t hops, const Filter& filter)
{
  // Look for the person's name.
  std::pair< bool, std::list<const Person *> > src_lst = database->search_person(source);

  // If the person is not found or its name matches more than once, then build an explanation response.
  if ( !src_lst.first || src_lst.second.size() != 1 ) {
    response << "<explain>";
    build_explain_response(source, src_lst);
    response << "</explain>";
  } else {
    // Computes the persons at a distance of N from source.
    std::list<const Person *> result = database->distance_n( src_lst.second.front(), hops, filter );
    
    // Response message...
    response << "<distance_n source=\"" << xml_string_escape(source) << "\" hops=\"" << hops << "\">";
    for(std::list<const Person *>::const_iterator it = result.begin(); it != result.end(); ++it) {
      response << "<person>" << xml_string_escape((*it)->name()) << "</person>";
    }
    response << "</distance_n>";
  }
}

void TCPClientSession::build_center_response(const std::string& source, const Filter& filter)
{
  // Look for the person's name.
  std::pair< bool, std::list<const Person *> > src_lst = database->search_person(source);
  
  // If the person is not found or its name matches more than once, then build an explanation response.
  if ( !src_lst.first || src_lst.second.size() != 1 ) {
    response << "<explain>";
    build_explain_response(source, src_lst);
    response << "</explain>";
  } else {
    // It computes the average number of hops.
    float result = database->center( src_lst.second.front(), filter );

    // Response message.
    response << "<center source=\"" << source << "\">" << result << "</center>";
  }
}

void TCPClientSession::print_log_request(const std::string& request)
{
  log_mutex.enterMutex();
  logfile << get_datetime() << " " << client_ip << ": " << std::endl 
	  << "=============== XML REQUEST: ===============" << std::endl
	  << request << std::endl
	  << "============================================" << std::endl;
  log_mutex.leaveMutex();
}

void TCPClientSession::print_log_response(const std::string& response)
{
  log_mutex.enterMutex();
  logfile << get_datetime() << " " << client_ip << ": " << std::endl 
	  << "=============== XML RESPONSE: ===============" << std::endl
	  << response << std::endl
	  << "=============================================" << std::endl;
  log_mutex.leaveMutex();
}
